/* 2008 8 29 */

static const char *__my_host, *__my_user, *__my_pass, *__my_name;
static int   __my_port;

/* used to check db_initialize() is called. */
static int is_initialized = 0;

#include "mysql_client.h"
#include "url.h"
#include "db.h"

static Mysql *mydb;

#include "log.h"

#include <iostream>

static const std::string make_sql_string(const char* action, std::map<std::string, std::string> &m)
{
	std::map<std::string, std::string>::iterator it;
	std::string sqlstring;

	m.erase("a");
	if((it = m.find("t")) == m.end())
	{
		logger(PRIORITY_DEBUG, "no table name found, please set 't=[table]'.");
		return "";
	}
	std::string table = it->second;
	m.erase(it);
	
	if(   strcasecmp(action, "insert" ) == 0
	   || strcasecmp(action, "replace") == 0)
	{
		if(m.empty()){
			logger(PRIORITY_DEBUG, "no insert value found.");
			return "";
		}		

		sqlstring  = action;
		sqlstring += " INTO `" + table + "` SET ";
		it = m.begin();
		sqlstring += "`" + it->first + "`='" + it->second + "'";
		for(++it; it != m.end(); ++it)
			sqlstring += ", `" + it->first + "`='" + it->second + "'";
		
		/* INSERT or REPLACE statment made, success return */
		return sqlstring;
	}

	/* auto make ALTER sql statment is not suported */
	if(   strcasecmp(action, "alter") == 0
	   || strcasecmp(action, "show" ) == 0) {
		logger(PRIORITY_DEBUG, "auto make ALTER or SHOW sql statment is not suported.");
		return "";
	}

	std::string condition = "";
	std::string limit     = "";
	/* get condition behind keyword 'where' */
	if((it = m.find("c")) == m.end())
	{
		logger(PRIORITY_WARN, "no input hehind keyword 'where' found. "
				      "if you really want to use no where statment, set 'c=none'"); 
		return "";
	}

	if(it->second != "none")
		condition = " WHERE " + it->second;
	m.erase(it);

	/* get limit string */
	if((it = m.find("l")) != m.end()) {
		limit = " LIMIT " + it->second;
		m.erase(it);
	}

	if(strcasecmp(action, "update") == 0) {
		if(m.empty()) {
			logger(PRIORITY_DEBUG, "no update value found. omit this item.");
			return "";
		}

		sqlstring = "UPDATE `" + table + "` SET ";
		it = m.begin();
		sqlstring += "`" + it->first + "`='" + it->second + "'"; 
		while(++it != m.end())
			sqlstring += ", `" + it->first + "`='" + it->second + "'";
		sqlstring += condition + limit;

		/* UPDATE statment success return. */
		return sqlstring;
	}

	if(strcasecmp(action, "delete") == 0)
		return "DELETE FROM " + table + condition + limit; 

	/* read only statment or others will be omited, just return a simpile statment. */
	return "show tables";
}

static bool is_sql_read_statment(const char* sql)
{
	return     strncasecmp(sql, "select", 6) == 0
		|| strncasecmp(sql, "show"  , 4) == 0;
}

static bool is_sql_statment(const char* sql)
{
	return     strncasecmp(sql, "select" , 6) == 0
		|| strncasecmp(sql, "update" , 6) == 0
		|| strncasecmp(sql, "insert" , 6) == 0
		|| strncasecmp(sql, "replace", 6) == 0
		|| strncasecmp(sql, "delete" , 6) == 0
		|| strncasecmp(sql, "create" , 6) == 0
		|| strncasecmp(sql, "alter"  , 5) == 0
		|| strncasecmp(sql, "show"   , 4) == 0
		|| strncasecmp(sql, "drop"   , 4) == 0;
}


#ifdef  __cplusplus  //__cpplusplus
extern "C" {
#endif

int db_initialize()
{
	/* load read only database */
	const char* port;

	__my_host = INI_GetValue(cfg, "db", "DBHost");
	if(__my_host == NULL) __my_host = "localhost";

	__my_user = INI_GetValue(cfg, "db", "DBUser");
	if(__my_user == NULL) __my_user = "";
	
	__my_pass = INI_GetValue(cfg, "db", "DBPass");
	if(__my_pass == NULL) __my_pass = "";
	
	__my_name = INI_GetValue(cfg, "db", "DBName");	
	if(__my_name == NULL) __my_name = "";

	     port = INI_GetValue(cfg, "db", "DBPort");
	__my_port = port == NULL ? 3306 : atoi(port);

	mydb = new Mysql(__my_name, __my_host, __my_user, __my_pass, __my_port);
	if(!mydb->available()) {
		logger(PRIORITY_ERROR, "database is not available.");
		delete mydb;
		mydb = NULL;
		return -1;
	}

	logger(PRIORITY_DEBUG, "database connect success.");
	is_initialized = 1;
	return 1;
}

void db_destroy()
{
	delete mydb;
	mydb = NULL;
	logger(PRIORITY_DEBUG, "database connect closed.");
	is_initialized = 0;
}

/* return value:
 *	-1	some error occured, if -1 is return, the db rqueue will locked.
 *	0	function called but no effect made.
 *	1	success.
 */
int db_handler(const queue_header* header, const void* data)
{
	std::map<std::string, std::string> mdata;
	std::map<std::string, std::string>::iterator it;
	const char *sql = NULL;
	char action[16];

	/* load db accout from ini file, create mysql connection. ext. */
	if(!is_initialized && db_initialize() != 1)
		return -1;
	
	if(!parse_query((const char*)data, header->size, mdata))
	{
		logger(PRIORITY_DEBUG, "parse query error, data: %s", (const char*)data);
		return 0;
	}

	/* check is there any sql statement exist. */
	if((it = mdata.find("s")) != mdata.end())
		sql = it->second.c_str();

	/* start check the action argment */
	if((it = mdata.find("a")) == mdata.end())
	{
		if(sql == NULL) {
			logger(PRIORITY_DEBUG, "both 'a'(action) and 's'(statment) not exist,"
					       " give up. data:%s", (char*)data);
			return 0;
		}

		if(!is_sql_statment(sql))
		{
			logger(PRIORITY_DEBUG, "'s'(statment) found but it not sames a sql statment: '%s'", sql);
			return 0;
		}

		logger(PRIORITY_WARN, "'a'(action) not found, treat as \"sql\" command.");
		strcpy(action, "sql");
	}
	else
	{
		strncpy(action, it->second.c_str(), 15);
		action[15] = 0;
	}

	if(   strncasecmp(action, "sql"     , 3) == 0
	   || strncasecmp(action, "statment", 8) == 0
	   || strncasecmp(action, "query"   , 5) == 0
	   || strncasecmp(action, "excute"  , 6) == 0)
	{
		/* it a dircet excutable sql statment */
		if(sql == NULL) {
			logger(PRIORITY_DEBUG, "sql statment 'a'(action) command, but no sql string found.");
			return 0;
		}
	}

	/* no dircet sql statment, we try to make one. */
	std::string sqlstr;
	if(sql == NULL)
	{
		sqlstr = make_sql_string(action, mdata);
		if(!sqlstr.empty())
			sql = sqlstr.c_str();
	}

	if(sql == NULL)
	{
		logger(PRIORITY_DEBUG, "make sql statment error.");
		return 0;
	}

	if(is_sql_read_statment(sql)) {
		logger(PRIORITY_DEBUG, "just a read only sql statment, give up.");
		return 0;
	}

#ifdef DEBUG
	fprintf(stderr, "==== sql ===\n%s\n", sql);
#endif
	/* true or false (1 or 0) will returned */
	return mydb->execute(sql, strlen(sql));
}

#ifdef  __cplusplus
}
#endif
